﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TtfLibrary
{
    [TtfStructure]
    public class TtfFormat6Encoding: IAfterRead, IAfterWrite
    {
        public UInt16 Length;
        public UInt16 Language;
        public UInt16 FirstCode;
        public UInt16 EntryCount;
        [TtfPart("GlyphIdArray")]
        public UInt16[] GlyphIdArray;

        public void AfterRead(TtfStructureReader reader, int startOffset)
        {
            GlyphIdArray = new ushort[EntryCount];
            reader.Read(this, new[] { "GlyphIdArray" });
        }

        public void AfterWrite(StructureWriter writer)
        {
            writer.WriteObject(this, new[] { "GlyphIdArray" });
        }
    }

    [TtfStructure]
    public class TtfFormat4Encoding : IAfterRead, IAfterWrite
    {
        //public UInt16 Format; //Format number is set to 4.
        public UInt16 Length; //This is the length in bytes of the subtable.
        public UInt16 Language; //Please see “Note on the language field in 'cmap' subtables“ in this document.
        public UInt16 SegCountX2; //2 x segCount.
        public UInt16 SearchRange; //2 x (2**floor(log2(segCount)))
        public UInt16 EntrySelector; //log2(searchRange/2)
        public UInt16 RangeShift; //2 x segCount - searchRange
        [TtfPart("Continue")]
        public UInt16[] EndCount; //End characterCode for each segment, last=0xFFFF.
        public UInt16 ReservedPad; //Set to 0.
        public UInt16[] StartCount; //Start character code for each segment.
        public Int16[] IdDelta; //Delta for all character codes in segment.
        public UInt16[] IdRangeOffset; //Offsets into glyphIdArray or 0
        [TtfPart("GlyphIdArray")]
        public UInt16[] GlyphIdArray; //Glyph index array (arbitrary length)

        public void AfterRead(TtfStructureReader reader, int startOffset)
        {
            var segCount = SegCountX2 / 2;
            this.EndCount = new UInt16[segCount];
            this.StartCount = new UInt16[segCount];
            this.IdDelta = new Int16[segCount];
            this.IdRangeOffset = new UInt16[segCount];
            reader.Read(this, new[] { "Continue" });
            
            var glyphCount = (this.Length - (reader.Position - startOffset)) / 2;
            this.GlyphIdArray = new UInt16[glyphCount];
            reader.Read(this, new[] { "GlyphIdArray" });
        }

        public void AfterWrite(StructureWriter writer)
        {
            writer.WriteObject(this, new[] { "Continue", "GlyphIdArray" });
        }
    }

    [TtfStructure]
    public class TtfFormat0Encoding
    {
        public UInt16 Length; // This is the length in bytes of the subtable.
        public UInt16 Language; // Please see “Note on the language field in 'cmap' subtables“ in this document.
        public byte[] GlyphIdArray = new byte[256]; //An array that maps character codes to glyph index values.
    }

    [TtfStructure]
    public class TtfEncodingRecord
    {
        public static int ContentLength = 2 + 2 + 4;

        public UInt16 PlatformId;
        public UInt16 EncodingId;
        public UInt32 Offset;

        [TtfStop]

        public TtfCmapSubtable Table;
    }

    public class TtfCmapSubtable: IAfterRead, IAfterWrite
    {
        public UInt16 Format;
        [TtfPart("Format0")]
        public TtfFormat0Encoding Format0;
        [TtfPart("Format4")]
        public TtfFormat4Encoding Format4;
        [TtfPart("Format6")]
        public TtfFormat6Encoding Format6;

        public void AfterRead(TtfStructureReader reader, int startOffset)
        {
            if (this.Format == 0)
                reader.Read(this, new[] { "Format0" });
            else if (this.Format == 4)
                reader.Read(this, new[] { "Format4" });
            else if (this.Format == 6)
                reader.Read(this, new[] { "Format6" });
        }

        public void AfterWrite(StructureWriter writer)
        {
            if (this.Format == 0)
                writer.WriteObject(this, new[] { "Format0" });
            else if (this.Format == 4)
                writer.WriteObject(this, new[] { "Format4" });
            else if (this.Format == 6)
                writer.WriteObject(this, new[] { "Format6" });
        }        
    }

    [TtfStructure]
    public class OttCmap: IAfterRead, IAfterWrite
    {
        [TtfPart("Default")]
        public UInt16 Version;
        public UInt16 NumTables;
        [TtfPart("EncodingRecords")]
        public TtfEncodingRecord[] EncodingRecords;

        public void AfterRead(TtfStructureReader reader, int startOffset)
        {
            reader.Read(this, new[] { "Default" });
            this.EncodingRecords = new TtfEncodingRecord[this.NumTables];
            reader.Read(this, new[] { "EncodingRecords" });

            var tables = new Dictionary<uint, TtfCmapSubtable>();
            foreach (var offset in this.EncodingRecords.Select(r => r.Offset).Distinct())
            {
                reader.Position = (int)(startOffset + offset);
                var subtable = new TtfCmapSubtable();
                reader.Read(subtable);
                tables.Add(offset, subtable);
            }

            foreach (var rec in this.EncodingRecords)
                rec.Table = tables[rec.Offset];
        }

        public void AfterWrite(StructureWriter writer)
        {
            var tableStartPos = writer.Stream.Position;

            this.NumTables = (ushort)this.EncodingRecords.Length;
            writer.WriteObject(this, new[] { "Default" });

            var encRecStart = writer.Stream.Position;
            writer.Stream.Position += this.EncodingRecords.Length * TtfEncodingRecord.ContentLength;

            var offsets = new Dictionary<TtfCmapSubtable, uint>();

            foreach (var table in this.EncodingRecords.Select(r => r.Table).Distinct())
            {
                offsets[table] = (uint)(writer.Stream.Position - tableStartPos);
                writer.WriteObject(table);
            }

            var endPos = writer.Stream.Position;

            foreach (var rec in this.EncodingRecords)
                rec.Offset = offsets[rec.Table];

            writer.Stream.Position = encRecStart;
            writer.WriteObject(this, new[] { "EncodingRecords" });

            writer.Stream.Position = endPos;
        }
    }
}
